package com.zm.openapi.config;

import com.zm.openapi.model.SimpleApiResult;
import com.zm.openapi.exception.ValidationException;
import com.zm.openapi.result.SystemResultEnum;
import com.zm.openapi.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ResponseStatusException;

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @description: 自定义错误处理自动配置类
 * @module: com.zm.apigateway.config
 * @author: songxiongli
 * @date: 2020-03-14 11:36
 **/
@SpringBootConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
@Slf4j
public class GatewayErrorWebFluxAutoConfiguration {
    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    @Value("${spring.application.name}")
    private String applicationName;

    public GatewayErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
                                                ResourceProperties resourceProperties,
                                                ObjectProvider<ViewResolver> viewResolversProvider,
                                                ServerCodecConfigurer serverCodecConfigurer,
                                                ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.orderedStream()
                .collect(Collectors.toList());
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
    @Order(-1)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonErrorWebExceptionHandler exceptionHandler = new JsonErrorWebExceptionHandler(
                errorAttributes,
                resourceProperties,
                this.serverProperties.getError(),
                applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }

    @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    }

    private class JsonErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

        public JsonErrorWebExceptionHandler(ErrorAttributes errorAttributes,
                                            ResourceProperties resourceProperties,
                                            ErrorProperties errorProperties,
                                            ApplicationContext applicationContext) {
            super(errorAttributes, resourceProperties, errorProperties, applicationContext);
        }

        @Override
        protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
            // 这里其实可以根据异常类型进行定制化逻辑
            Throwable error = super.getError(request);
            log.error(error.getMessage(), error);
            SimpleApiResult apiResult = null;
            if (error instanceof ResponseStatusException) {
                ResponseStatusException be = (ResponseStatusException) error;
                apiResult = new SimpleApiResult("httpstatus-error", be.getMessage());
            } else if (error instanceof BusinessException) {
                BusinessException be = (BusinessException) error;
                apiResult = new SimpleApiResult(be.getCode(), be.getMessage());
            } else if(error instanceof ValidationException) {
                ValidationException be = (ValidationException) error;
                apiResult = new SimpleApiResult(be.getCode(), be.getMsg());
            } else {
                apiResult = new SimpleApiResult(SystemResultEnum.SERVICE_ERROR_NICE_TIP, null,
                        applicationName + ", " + error.getMessage());
            }
            Map map = convert(apiResult);
            map.remove("serialVersionUID");
            return map;
        }

        @Override
        protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
            return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
        }

        @Override
        protected int getHttpStatus(Map<String, Object> errorAttributes) {
            // 这里其实可以根据errorAttributes里面的属性定制HTTP响应码
            return HttpStatus.OK.value();
        }
    }


    private <S> Map<String, Object> convert(S source) {
        if (source == null)
            return null;

        Map<String, Object> map = new LinkedHashMap<String, Object>();
        Class clazz = source.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = StringUtils.EMPTY;
            try {
                value = field.get(source);
            } catch (Exception ex) {
                log.error(ex.getMessage(), ex);
            }
            map.put(fieldName, value);
        }
        return map;
    }
}
